<?php

namespace GuanChanghu\Library\Models;


use GuanChanghu\Library\Facades\Client;
use GuanChanghu\Configs\RegularConfig;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;
use Spatie\Permission\Traits\HasRoles;
use GuanChanghu\Models\Traits\Attribute\Facade as Attribute;
use GuanChanghu\Models\Traits\Change\Facade as Change;
use GuanChanghu\Models\Traits\Query\Facade as Query;

/**
 * @author 管昌虎
 * Class User
 * @tag encryption free
 * @package GuanChanghu\Library\Models\User
 * Created on 2022/3/26 14:34
 * Created by 管昌虎
 * Email guanchanghu626@163.com
 */
class User extends Authenticatable implements \GuanChanghu\Models\Contracts\Recycle, \GuanChanghu\Models\Contracts\TableAttribute
{
    use HasRoles, HasFactory, Notifiable;

    use Attribute, Change, Query;

    /**
     * @var bool
     */
    public static $snakeAttributes = false;

    /**
     * 性别-女性
     */
    public const GENDER_FEMALE = 0;

    /**
     * 性别-男性
     */
    public const GENDER_MALE = 1;

    /**
     * 性别-未知
     */
    public const GENDER_UNKNOWN = 2;

    /**
     * The attributes that are mass assignable.
     *
     * @var string[]
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * @var string
     */
    public static string $nonexistent = '用户不存在';

    /**
     * @var array
     */
    public array $htmlFields = [];

    /**
     * 用户角色list redis key
     */
    public const ROLE_KEY_PREFIX = 'user:role:key';

    /**
     * 用户角色list redis key 过期时间
     */
    public const ROLE_KEY_EXPIRE = 60 * 60 * 24 * 30 * 7;

    /**
     * 用户权限list redis key
     */
    public const PERMISSION_KEY_PREFIX = 'user:permission:key';

    /**
     * 用户权限list redis key
     */
    public const PERMISSION_KEY_EXPIRE = 60 * 60 * 24 * 30 * 3;

    /**
     * @var array
     */
    protected array $clientPermissions = [];

    /**
     * @var array
     */
    protected array $clientRoles = [];

    /**
     * @var array
     */
    protected $appends = [
        'account'
    ];

    /**
     * 获得账号类型
     * @param string $account
     * @return string
     */
    public static function getAccountType(string $account): string
    {
        if (preg_match(RegularConfig::EMAIL, $account)) {
            return 'email';
        }
        if (preg_match(RegularConfig::MOBILE, $account)) {
            return 'mobile';
        }

        return 'username';
    }

    /**
     * @return HasMany
     */
    public function userClients(): HasMany
    {
        return $this->hasMany(config('guan-changhu.models.user_client'), 'user_id');
    }

    /**
     * @return Collection
     */
    public function getAllClient(): Collection
    {
        return $this['userClients']->pluck('client');
    }

    /**
     * @param string $client
     * @return array
     */
    public function getRoles(string $client): array
    {
        if (!Client::clientPermissionSwitch()) {
            return [];
        }
        if (isset($this->clientRoles[$client])) {
            return $this->clientRoles[$client];
        }

        $storeKey = static::ROLE_KEY_PREFIX . ':' . 'userId' . ':' . $this->getKey() . ':client:' . $client;

        if (Redis::exists($storeKey)) {
            $result = Redis::lRange($storeKey, 0, -1);
        } else {
            $result = $this->getRolesByDatabase($client);

            call_user_func_array([Redis::class, 'rPush'], array_merge([$storeKey], $result));

            Redis::expire($storeKey, static::ROLE_KEY_EXPIRE);
        }

        $this->clientRoles[$client] = $result;

        return $result;
    }

    /**
     * @param string $client
     * @return array
     */
    public function getPermissions(string $client): array
    {
        if (!Client::clientPermissionSwitch()) {
            return [];
        }

        if (isset($this->clientPermissions[$client])) {
            return $this->clientPermissions[$client];
        }

        $storeKey = static::PERMISSION_KEY_PREFIX . ':' . 'userId' . ':' . $this->getKey() . ':client:' . $client;

        if (Redis::exists($storeKey)) {
            $result = Redis::lRange($storeKey, 0, -1);
        } else {
            $result = $this->getPermissionsByDatabase($client);

            call_user_func_array([Redis::class, 'rPush'], array_merge([$storeKey], $result));

            Redis::expire($storeKey, static::PERMISSION_KEY_EXPIRE);
        }

        $this->clientPermissions[$client] = $result;

        return $result;
    }

    /**
     * @param string $client
     */
    public function clearPermissionRedis(string $client): void
    {
        $storeRoleKey = static::ROLE_KEY_PREFIX . ':' . 'userId' . ':' . $this->getKey() . ':client:' . $client;
        $storePermissionKey = static::PERMISSION_KEY_PREFIX . ':' . 'userId' . ':' . $this->getKey() . ':client:' . $client;
        Redis::del($storeRoleKey);
        Redis::del($storePermissionKey);
    }


    /**
     * @param string $client
     * @return array
     */
    public function getRolesByDatabase(string $client): array
    {
        $result = [];
        $roles = $this->roles()->where('client_name', $client)->pluck('name');

        $clientLength = strlen($client);

        $roles->each(function ($role) use (&$result, $clientLength) {
            $result[] = Str::substr($role, $clientLength + 1);
        });

        return $result;
    }

    /**
     * @param string $client
     * @return array
     */
    public function getPermissionsByDatabase(string $client): array
    {
        $permissions = [];
        $permissionModels = $this->getAllPermissions()->where('client_name', $client)->pluck('name');

        $clientLength = strlen($client);

        $permissionModels->each(function ($permission) use (&$permissions, $clientLength) {
            $permissions[] = Str::substr($permission, $clientLength + 1);
        });

        return $permissions;
    }

    /**
     * @return static
     */
    public static function getRootAccount(): static
    {
        return static::query()->where('username', config('guan-changhu.root_account'))->first();
    }

    /**
     * 账号类型
     * @return string
     */
    public function accountType(): string
    {
        if ($this['username']) {
            return 'username';
        }
        if ($this['mobile']) {
            return 'mobile';
        }
        if ($this['email']) {
            return 'email';
        }

        return '';
    }

    /**
     * 获得账号
     * @return string
     */
    public function account(): string
    {
        if ($this['username']) {
            return $this['username'];
        }
        if ($this['mobile']) {
            return $this['mobile'];
        }
        if ($this['email']) {
            return $this['email'];
        }
        return '';
    }

    /**
     * 根据账户获得用户信息
     * @param string $account
     * @return static|Builder|Model|null
     */
    public static function getUserByAccount(string $account): static|Builder|Model|null
    {
        return static::query()->where(static::getAccountType($account), $account)->first();
    }

    /**
     * 根据用户名获得用户信息
     * @param string $username
     * @return static|Builder|Model|null
     */
    public static function getUserByUsername(string $username): static|Builder|Model|null
    {
        return static::query()->where('username', $username)->first();
    }

    /**
     * 根据手机号获得用户信息
     * @param string $mobile
     * @return static|Builder|Model|null
     */
    public static function getUserByUMobile(string $mobile): static|Builder|Model|null
    {
        return static::query()->where('mobile', $mobile)->first();
    }

    /**
     * 根据邮箱获得用户信息
     * @param string $email
     * @return static|Builder|Model|null
     */
    public static function getUserByEmail(string $email): static|Builder|Model|null
    {
        return static::query()->where('email', $email)->first();
    }

    /**
     * 根据账户判断是否注册
     * @param string $account
     * @param string $client
     * @return bool
     */
    public static function isRegisterByAccount(string $account, string $client = ''): bool
    {
        $builder = static::query()->where(static::getAccountType($account), $account);

        if ($client) {
            $builder->whereHas('userClients', function ($query) use ($client) {
                $query->where('client', $client);
            });
        }

        return $builder->exists();
    }


    /**
     * 根据用户名判断是否注册
     * @param string $username
     * @param string $client
     * @return bool
     */
    public static function isRegisterByUsername(string $username, string $client = ''): bool
    {
        $builder = static::query()->where('username', $username);

        if ($client) {
            $builder->whereHas('userClients', function ($query) use ($client) {
                $query->where('client', $client);
            });
        }

        return $builder->exists();
    }

    /**
     * 根据手机号判断是否注册
     * @param string $mobile
     * @param string $client
     * @return bool
     */
    public static function isRegisterByMobile(string $mobile, string $client = ''): bool
    {
        $builder = static::query()->where('mobile', $mobile);

        if ($client) {
            $builder->whereHas('userClients', function ($query) use ($client) {
                $query->where('client', $client);
            });
        }

        return $builder->exists();
    }

    /**
     * 根据邮箱判断是否注册
     * @param string $email
     * @param string $client
     * @return bool
     */
    public static function isRegisterByEmail(string $email, string $client = ''): bool
    {
        $builder = static::query()->where('email', $email);

        if ($client) {
            $builder->whereHas('userClients', function ($query) use ($client) {
                $query->where('client', $client);
            });
        }

        return $builder->exists();
    }
}
